/*	Copyright 2007 - Xavier Baro (xbaro@cvc.uab.cat)

	This file is part of eapmlib.

    Eapmlib is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or any 
	later version.

    Eapmlib is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "WLEAPMDipole.h"

Evolutive::CWLEAPMDipole::CWLEAPMDipole(void) : CABWeakLearner(),m_SamplesLabel(NULL),m_NumSamples(0),
					m_NumIters(100),m_ModelTolerance(0.005),m_PopSize(100),m_NumElitism(4),m_MaxStaticIterations(50),
					m_ModelEstimationPer(0.75),m_VarNumBits(NULL),m_NumVariables(0)
{
#ifndef USE_OPENCV
	OPENCV_ERROR_CLASS("CWLEAPMDipole");
#else
	// Define the fixed parameters
	m_SmpEvaluator.SetErrorFunction(ERROR_WEIGHTED);
	m_Learner.SetCodingMethod(CODE_DECIMAL_INT);
	m_Learner.SetOptCriteria(OPT_MINIMIZE);
	m_Learner.SetEvolStrategy(EVOL_ESTIMATE);	

	// Create the feature prototype
	m_FeatureType=WLD_WDIPOLES;
	m_Dipole.SetEvalType(Evolutive::DIPEVAL_MEAN);
	m_Dipole.SetMinArea(9);
	m_Classifier.SetFeature(&m_Dipole);

	// By default, if the threshold and the feature value are equal, return a negative value.
	m_DefZeroValue=EQTHR_NEG;
	m_Classifier.SetThrEqualValueAction(m_DefZeroValue);
#endif
}

Evolutive::CWLEAPMDipole::~CWLEAPMDipole(void)
{
	if(m_SamplesLabel)
		delete[] m_SamplesLabel;
	if(m_VarNumBits)
		delete[] m_VarNumBits;
}

#ifdef USE_OPENCV
int Evolutive::CWLEAPMDipole::GetNumSamples(void)
{
	// Verify that we have the samples set
	if(!m_SamplesLabel)
		throw CEvolutiveLibException("Undefined Samples set",__FILE__,__LINE__,"GetNumSamples");

	// Returns the number of samples
	return m_NumSamples;
}

int* Evolutive::CWLEAPMDipole::GetLabelsPtr(void)
{
	// Verify that we have the samples set
	if(!m_SamplesLabel)
		throw CEvolutiveLibException("Undefined Samples set",__FILE__,__LINE__,"GetLabelsPtr");
	
	// Returns a pointer to the translated labels
	return m_SamplesLabel;
}

void Evolutive::CWLEAPMDipole::SetSamplesSet(Evolutive::CIntSamplesSet *SamplesSet)
{
	// Verify that the given samples set is correct
	if(!SamplesSet)
		throw CEvolutiveLibException("NULL pointer to a Samples set",__FILE__,__LINE__,"SetSamplesSet");

	// Store the samples set
	m_SamplesSet=SamplesSet;

	// Store the number of samples
	m_NumSamples=SamplesSet->GetNumSamples();

	// Create a binary representation in the set {-1,1} of the samples
	StoreLabels(SamplesSet);

	// Set all the parameters related to the samples set
	ApplySamplesParameters();
}

void Evolutive::CWLEAPMDipole::SetProbModel(Evolutive::CProbModel *Model)
{
	// Assign the probability model to the learner
	m_Learner.SetProbModel(Model);	
}

void Evolutive::CWLEAPMDipole::StoreLabels(Evolutive::CIntSamplesSet *SamplesSet)
{
	double Vals[2];
	int NumVals;
	bool Find;
	register int i,j;

	// Release old memory
	if(m_SamplesLabel)
		delete[] m_SamplesLabel;

	// Check the expected values in the training set
	Vals[0]=cvRound(SamplesSet->ExpectedVal(0));
	NumVals=1;
	for(i=0;i<m_NumSamples;i++)
	{
		Find=false;
		for(j=0;j<NumVals;j++)
		{
			if(cvRound(SamplesSet->ExpectedVal(i))==Vals[j])
			{
				Find=true;
				break;
			}
		}

		if(!Find)
		{
			if(NumVals<2)
			{
				Vals[1]=cvRound(SamplesSet->ExpectedVal(i));
				NumVals++;
			}
			else
			{
				// The samples set is not a binary problem. Error!!!
				throw CEvolutiveLibException("The input training set do not correspond to a binary problem.",__FILE__,__LINE__,"StoreLabels");
			}
		}
	}

	// Allocate the new memory for the labels
	m_SamplesLabel=new int[m_NumSamples];
	
	// Copy the labels
	if((Vals[0]==1 && Vals[1]==-1) || (Vals[0]==-1 && Vals[1]==1))
	{
		// If the labels are correct just copy them
		for(i=0;i<m_NumSamples;i++)
		{
			m_SamplesLabel[i]=cvRound(SamplesSet->ExpectedVal(i));
		}
	}
	else
	{
		// The higher value will be used as the class +1 and the lower as the class -1
		if(Vals[0]>Vals[1])
		{
			for(i=0;i<m_NumSamples;i++)
			{
				if(cvRound(SamplesSet->ExpectedVal(i))==Vals[0])
					m_SamplesLabel[i]=1;
				else
					m_SamplesLabel[i]=-1;
			}
		}
		else
		{
			for(i=0;i<m_NumSamples;i++)
			{
				if(cvRound(SamplesSet->ExpectedVal(i))==Vals[0])
					m_SamplesLabel[i]=-1;
				else
					m_SamplesLabel[i]=1;
			}
		}
	}
}

void Evolutive::CWLEAPMDipole::ApplySamplesParameters(void)
{
	CvSize ImageSize;
	bool UseColor;
	int NumBitsW,NumBitsH;

	// Remove the old memory
	if(m_VarNumBits)
		delete[] m_VarNumBits;

	// Allocate the new memory to describe the variables
	switch(m_FeatureType)
	{
	case WLD_DIPOLES:
		m_NumVariables=8;
		break;
	case WLD_WDIPOLES:
		m_NumVariables=11;
		break;
	case WLD_HAAR:
		m_NumVariables=5;
	}
	m_VarNumBits=new int[m_NumVariables];
	
	// Get the samples set parameters
	ImageSize=m_SamplesSet->GetSamplesSize();
	UseColor=m_SamplesSet->GetColorInfo();
	NumBitsW=static_cast<int>(ceil(log((double)ImageSize.width)/log(2.0)));
	NumBitsH=static_cast<int>(ceil(log((double)ImageSize.height)/log(2.0)));

	// Set the values for each variable
	switch(m_FeatureType)
	{
	case WLD_WDIPOLES:		
		m_VarNumBits[8]=1;
		m_VarNumBits[9]=1;
		m_VarNumBits[10]=1;
	case WLD_DIPOLES:		
		m_VarNumBits[0]=NumBitsW;
		m_VarNumBits[1]=NumBitsH;
		m_VarNumBits[2]=NumBitsW-1;
		m_VarNumBits[3]=NumBitsH-1;	
		m_VarNumBits[4]=NumBitsW;
		m_VarNumBits[5]=NumBitsH;
		m_VarNumBits[6]=NumBitsW-1;
		m_VarNumBits[7]=NumBitsH-1;
		break;
	case WLD_HAAR:
		m_VarNumBits[0]=NumBitsW;
		m_VarNumBits[1]=NumBitsH;
		m_VarNumBits[2]=NumBitsW-1;
		m_VarNumBits[3]=NumBitsH-1;	
		m_VarNumBits[4]=3;
		break;
	}

	// Prepare the feature prototype
	m_Classifier.SetFilter(true,ImageSize,UseColor);

	// Prepare the samples evaluator
	m_SmpEvaluator.SetClassifierObject((CClassifier*)&m_Classifier);
	m_SmpEvaluator.SetSamplesSet(m_SamplesSet);
	m_SmpEvaluator.SetProblemDesc(m_NumVariables,m_VarNumBits);		

	// Store a pointer to the samples evaluator
	m_Learner.SetEvaluator(&m_SmpEvaluator);	
}

Evolutive::CClassifier* Evolutive::CWLEAPMDipole::GetWeakClassifier(double *Weights,bool *Mask,int *ClassOutput)
{
	CChromosome SolInd,BestInd;
	CThrClassifier *NewClassifier=NULL;
	CDissociatedDipole *NewFeature=NULL;
	double Error=1.0;
	double ErrorStop=0.25;
	double ErrStep=1.1;
	double BestError=1.0;
	int Gen,Generation;
	bool ValidFeature,ValidResult;
	CDissociatedDipole oTestDipole;
	CvSize SamplesSize;
	bool UseColor;
	int Polarity=1;

	// Assign the weights to the learner
	m_SmpEvaluator.SetSamplesWeight(Weights);
	
	// Configure the learner
	m_Learner.SetMaxIterations(m_NumIters);
	m_Learner.SetModelTolerance(m_ModelTolerance);
	m_Learner.SetPopulationSize(m_PopSize);	
	m_Learner.SetNumElitist(m_NumElitism);
	m_Learner.SetNumEstimation(cvRound(m_PopSize*m_ModelEstimationPer));	
	m_Learner.SetMaxStaticIterations(m_MaxStaticIterations);

	// Retrieve the samples parameters
	SamplesSize=m_SamplesSet->GetSamplesSize();
	UseColor=m_SamplesSet->GetColorInfo();
	

	// While the error is greater or equal than 0.5, relaunch the process
	Generation=0;
	ValidResult=false;
	while(BestError>=ErrorStop || !ValidResult)
	{
		// By default use a positive polarity
		Polarity=1;

		// Set the stop value (restriction)
		m_Learner.SetStopValue(ErrorStop);

		// Launch the evolutionary algorithm
		Error=m_Learner.Solve(SolInd,Gen);

		// As in a binary problem the error cannot be higher than 0.5, if this is the case, take the symmetric.
		if(Error>0.5)
		{
			Polarity=-1;
			Error=1.0-Error;
		}

		// Create the feature
		oTestDipole.SetParameters(SolInd.GetNumFields(),SolInd.GetDblStringPtr());

		// Verify the feature
		ValidFeature=oTestDipole.IsValid(SamplesSize,UseColor);

		// If the new error is better than the old, store the new feature
		if(Error<BestError && ValidFeature)
		{
			BestInd=SolInd;
			BestError=Error;
			ValidResult=true;
		}

		// Smooth the restrictions
		ErrorStop*=ErrStep;

		// Add the number of generations
		Generation+=Gen;
	}
	
	// Declare a new feature and a new classifier to be returned
	NewFeature=new CDissociatedDipole();
	NewClassifier=new CThrClassifier();

	// Set the feature parameters and create the classifier
	NewFeature->SetParameters(BestInd.GetNumFields(),BestInd.GetDblStringPtr());
	NewClassifier->SetFeature(NewFeature);
	NewClassifier->SetPolarity(Polarity);
	
	// In the case of equal values between classifier and threshold, return a positive value
	NewClassifier->SetThrEqualValueAction(m_DefZeroValue);
	NewClassifier->SetSize(SamplesSize.width,SamplesSize.height);

	// Classify the samples
	Classify(NewClassifier,ClassOutput);

	// Return the classifier
	return (CClassifier*)NewClassifier;
}

void Evolutive::CWLEAPMDipole::Classify(CClassifier *Classifier,int *Class)
{
	register int i;
	CData *pData=NULL;
	double GetVal;

	// Classify all the samples
	for(i=0;i<m_SamplesSet->GetNumSamples();i++)
	{
		// Point the data
		pData=m_SamplesSet->GetData(i);

		// Classify the sample
		GetVal=Classifier->Apply(pData);
		Class[i]=static_cast<int>(cvRound(GetVal));

		// Release the data
		delete pData;
	}
}

void Evolutive::CWLEAPMDipole::Classify(CClassifier *Classifier,double *Class)
{
	register int i;
	CData *pData=NULL;

	// Classify all the samples
	for(i=0;i<m_SamplesSet->GetNumSamples();i++)
	{
		// Point the data
		pData=m_SamplesSet->GetData(i);

		// Classify the sample
		Class[i]=Classifier->Apply(pData);
		
		// Release the data
		delete pData;
	}
}

void Evolutive::CWLEAPMDipole::SetNumIterations(int NumIters)
{
	m_NumIters=NumIters;
}

void Evolutive::CWLEAPMDipole::UpdateSamplesSet(CClassifier *Classifier)
{
	m_SamplesSet->UpdateSamples(Classifier);
}
#endif //USE_OPENCV